<?php
// 应用公共文件
use app\model\TimerQueue;
use app\result\TimeQueueResult;
use log\File;
use think\App;


/**
 * 全局成功返回函数
 * @param string $msg 消息内容
 * @param int $code 状态码
 * @param array $data 返回数据
 * @return void
 */
function data_return($msg = '操作成功', $code = 0, $data = [])
{
    data_exit_json([
        'code' => $code,
        'msg' => $msg,
        'data' => $data,
    ]);
}

/**
 * 全局失败返回函数
 * @param string $msg 消息内容
 * @param int $code 状态码
 * @param array $data 返回数据
 * @return array
 */
function data_return_arr($msg = '操作失败', $code = 1, $data = []): array
{
    return [
        'code' => $code,
        'msg' => $msg,
        'data' => $data,
    ];
}

/**
 * 直接输出JSON数据并终止执行
 * @param array $data
 */
function data_exit_json($data)
{
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}


/**
 * 数据验证
 * @param $data array 需要验证的数组对象
 * @param $rules array 规则 $rule[0]-字段，$rule[1]-规则项，$rule[2]-规则项对应的提示语
 * @return array
 * @author blog.alipay168.cn
 */
function check_param(array $data, array $rules)
{
    foreach ($rules as $ruleSet) {
        if (empty($ruleSet[0]) || empty($ruleSet[1]) || empty($ruleSet[2])) {
            return ['status' => -1, 'msg' => '内部错误'];
        }

        $field = $ruleSet[0];
        $ruleParts = explode('|', $ruleSet[1]);// number|between:1,150
        $messageParts = explode('|', $ruleSet[2]); //必须为数字|数字必须在1-150之间
        foreach ($ruleParts as $index => $rule) {
            $rule = trim($rule);
            //如：number|between:1,150|function:is_mobile|length:11
            if (stripos($rule, ':') !== false) {
                $ruleArr = explode(':', $rule);
                $rule = $ruleArr[0];//验证方式
                $match = $ruleArr[1];//对应方式
            }
            //如果不存在该字段就直接返回
            if (!isset($data[$field])) {
                return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数必须存在'];
            }
            switch ($rule) {
                case 'number':
                    if (!is_numeric($data[$field])) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数必须为数字'];
                    }
                    break;
                case 'float':
                    if (!is_float($data[$field]) && !is_int($data[$field])) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数必须为浮点数'];
                    }
                    break;
                case 'between':
                    [$min, $max] = explode(',', $match);
                    if ($data[$field] < $min || $data[$field] > $max) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数范围在' . ('在' . $min . '-' . $max . '之间')];
                    }
                    break;
                case 'in_array':
                    $values = explode(',', $match);
                    if (!in_array($data[$field], $values)) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数范围在' . implode(', ', $values) . '之中'];
                    }
                    break;
                case 'empty':
                    if (empty($data[$field])) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数必须存在'];
                    }
                    break;
                case 'length':
                    [, $len] = explode(',', $match);
                    if (strlen($data[$field]) != $len) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数长度为' . $len];
                    }
                    break;
                case 'max_length':
                    if (strlen($data[$field]) > $match) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数长度不能大于' . $len];
                    }
                    break;
                case 'min_length':
                    if (strlen($data[$field]) < $match) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数长度不能小于' . $len];
                    }
                    break;
                case 'min':
                    if (strlen($data[$field]) < $match) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数长度不能小于' . $min];
                    }
                    break;
                case 'max':
                    if (strlen($data[$field]) > $match) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数长度不能大于' . $max];
                    }
                    break;
                case 'custom':
                case 'func':
                case 'function':
                    if (empty($match)) {
                        return ['status' => -1, 'msg' => '内部错误,验证方法' . $rule . '不存在'];
                    }
                    $method = $match;
                    if (function_exists($method) || !is_callable($method)) {
                        return ['status' => -1, 'msg' => '内部错误,验证方法' . $method . '不存在'];
                    }
                    if (!$method($data[$field])) {
                        return ['status' => -1, 'msg' => $messageParts[$index] ?: $field . '参数不合法'];
                    }
                    break;
            }
        }
    }

    return ['status' => 0, 'msg' => 'success'];
}

function bs_curl_get($url, $data = [], $timeout = 10, $header = [])
{
    if ($url == "" || $timeout <= 0) {
        return false;
    }
    if ($data != []) {
        $url = $url . '?' . http_build_query($data);
    }
    $con = curl_init((string)$url);
    curl_setopt($con, CURLOPT_HEADER, false);
    curl_setopt($con, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($con, CURLOPT_SSL_VERIFYPEER, false); //信任任何证书
    curl_setopt($con, CURLOPT_SSL_VERIFYHOST, 0); // 检查证书中是否设置域名,0不验证
    if (!empty($headers)) {
        curl_setopt($con, CURLOPT_HTTPHEADER, $headers);
    }
    curl_setopt($con, CURLOPT_TIMEOUT, (int)$timeout);
    return curl_exec($con);
}


/**
 * 日志记录
 * @param $log
 * @param string $type 日志类型
 * @param string $fileName 日志文件名，如abc.log
 * @param string $driver 日志驱动，默认为file，可选mongo
 * @return void
 */
function bs_log($log, string $type = 'debug', string $fileName = '', string $driver = 'file')
{
    if ($driver == 'mongo') {
        if (class_exists('\log\Mongo')) {
            \log\Mongo::add($log, $type);
        } else {
            exit('\log\Mongo日志类不存在');
        }
    } elseif ($driver == 'file') {
        if (class_exists('\log\File')) {
            File::add($log, $type, $fileName);
        } else {
            exit('\log\File日志类不存在');
        }
    } else {
        (new think\Log((new App())))->write($log, $type);
    }
}


/**
 * 异步请求
 * @param $api
 * @param array $data
 * @param string $method
 * @param int $timeout 默认300毫秒
 * @return void
 */
function asyn_req($api, array $data = [], string $method = 'GET', int $timeout = 300)
{
    //bs_log(compact('api', 'data', 'method'), 'info', 'asyn_req.log');
    $ch = curl_init();

    if (strtolower($method) == 'get') {
        if ($data) $data = http_build_query($data);
        $api = $api . '?' . $data;
    }
    // 设置请求的URL和其他选项
    curl_setopt($ch, CURLOPT_URL, $api);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    // 设置为非阻塞模式
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
    //单位毫秒，太小可能因为来得及发起而请求失败
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeout);
    if ($method == 'get') {
        curl_setopt($ch, CURLOPT_HTTPGET, true);
    } else {
        curl_setopt($ch, CURLOPT_POST, true);
        if (is_array($data) || is_object($data)) {
            $data = http_build_query($data);
        }
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    // 设置回调函数处理响应结果
    curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $data) {
        // 处理响应数据
        // bs_log(compact('data','curl'),'res','asyn_req.log');
        return strlen($data);
    });
    // 发起异步请求
    curl_exec($ch);
    if ($err = curl_errno($ch)) {
        $err = curl_error($ch);
        //  bs_log(compact('err','api','data','method'),'error','asyn_req.log');
    }
    // 关闭cURL资源
    curl_close($ch);
}

/**
 * 添加任务队列
 * @param $class string
 * @param $method string
 * @param $param string|int|array
 * @param $start_time int
 * @param $later int 延迟时间,0=立即执行，单位秒
 * @param null $queue
 * @return false|int
 * @example yz_queue("app\\test\\controller\\Index",'c12',['abc'=>'xxx'],time());
 */
function bs_queue(string $class, string $method, $param, int $start_time = 0, $later = 0, $queue = null)
{
    //todo 可以自己屏蔽下面的设置，如果需要看这个sql
    $debug = config('database.debug');
    config(['debug' => false], 'database');//关闭数据库SQL日志
    $id = (new TimerQueue())->data_add([
        'start_time' => $start_time ?: time(),
        'class' => $class,
        'method' => $method,
        'param' => is_array($param) || is_object($param) ? json_encode($param, JSON_UNESCAPED_UNICODE) : $param,
    ]);

    if ($id) {
        $openSwitch = config('queue.openSwitch');
        if ($openSwitch) {
            //用队列执行
            $job = 'app\job\Task';
            if ($later) {
                think\facade\Queue::later($later, $job, ['id' => $id], $queue);
            } else {
                think\facade\Queue::push($job, ['id' => $id], $queue);
            }
        } else {
            //立即执行,不太建议
            $res = (new TimeQueueResult())->do_job($id);
            bs_log(compact('res', 'id'), 'res', 'queue_diy_task.log');
        }
    }
    config(['debug' => $debug], 'database');
    return $id;
}

// 获取客户端IP
function get_ip()
{
    $ip = $_SERVER['REMOTE_ADDR'];
    if (isset($_SERVER['HTTP_CDN_SRC_IP'])) {
        $ip = $_SERVER['HTTP_CDN_SRC_IP'];
    } elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) and preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
        foreach ($matches[0] as $xip) {
            if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
                $ip = $xip;
                break;
            }
        }
    }
    return $ip;
}
